<?php
namespace Swork;

use Swork\Bean\Holder\ExceptionHandlerHolder;
use Swork\Bean\Holder\InstanceHolder;
use Swork\Bean\Holder\TimerTaskHolder;
use Swork\Behavior\RequestInterface;
use Swork\Exception\RpcException;
use Swork\Handler\TcpHandler;
use Swork\Helper\DateHelper;
use Swork\Helper\StringHelper;
use Swork\Logger\Formatter;
use Swork\Logger\Logger;
use Swork\Process\Reload;
use Swork\Process\UserProcess;
use Swork\Server\Http\Argument;
use Swork\Server\Http\Request;
use Swork\Server\Rpc\RpcPackage;
use Swork\Server\Rpc\RpcReceive;
use Swork\Server\Task\TaskManager;
use Swork\Server\Tcp\TcpArgument;
use Swork\Server\Tcp\TcpHandlerInterface;
use Swork\Server\Tcp\TcpReceive;
use Swork\Server\Ws\WsArgument;
use Swork\Server\Ws\WsReceive;

/**
 * 服务入口
 * Class Service
 * @package Swork
 */
class Service
{
    /**
     * 全局环境参数
     * @var array
     */
    public static $env;

    /**
     * swoole服务对象
     * @var \swoole_websocket_server
     */
    public static $server;

    /**
     * 日志对象
     * @var Logger
     */
    public static $logger;

    /**
     * 进程PID管理器
     * @var PidManager
     */
    public static $pidManager;

    /**
     * 任务管理器
     * @var TaskManager
     */
    public static $taskManager;

    /**
     * 共享内存行数
     * @var int
     */
    public static $table_size = 0;

    /**
     * 共享内存Data\Num字段大小
     * @var int
     */
    private static $table_col_data_size = 0;
    private static $table_col_num_size = 0;

    /**
     * 是否开启定时任务
     * @var bool
     */
    private $timer_task = false;

    /**
     * WS连接fd
     * @var array
     */
    private $websocket_fds = [];

    /**
     * 请求处理口
     * @var string
     */
    private $request_handler = null;

    /**
     * 请求处理口实例
     * @var RequestInterface
     */
    private $request_handler_instance = null;

    /**
     * 是否开启热更新
     * @var bool
     */
    private $auto_reload = false;

    /**
     * Service constructor.
     * @param array $env
     */
    public function __construct($env)
    {
        self::$env = $env;
    }

    /**
     * 主进程启动
     * @param \swoole_server $server
     */
    public function onStart(\swoole_server $server)
    {
    }

    /**
     * 应用终止
     * @param \swoole_server $server
     */
    public function onShutdown(\swoole_server $server)
    {
        self::$pidManager->clear();
    }

    /**
     * Worker进程启动（含Task进程）
     * @param \swoole_server $server
     * @param int $workder_id
     */
    public function onWorkerStart(\swoole_server $server, int $workder_id)
    {
        //加载连接配置
        $inti = new Initialize();
        $scanner = $inti->collect($workder_id);
        $inti->db();
        $inti->redis();
        $inti->rpc();
        $inti->amqp();
        $inti->ElasticSearch();

        //重新生成随机种子
        mt_srand();

        //仅在主线程时执行
        if ($workder_id == 0)
        {
            //保存manager_pid、master_pid、当前worker_pid至文件
            self::$pidManager->appendPid($server->master_pid, 'master');
            self::$pidManager->appendPid($server->manager_pid, 'manager');
            self::$pidManager->appendPid($server->worker_pid, 'worker0');

            //初始化任务管理器
            self::$taskManager = new TaskManager(self::$env);
            self::$taskManager->setTasks($scanner->getTimerTask());

            //如果开启了任务
            if ($this->timer_task)
            {
                self::$taskManager->localTask();
            }
        }
        else
        {
            //记录非0号worker的PID（先发回0号worker，防止文件并行读写出现异常）
            $pidName = ($server->taskworker ? 'task' : 'worker') . $workder_id;
            self::$pidManager->invoke('appendPid', [$server->worker_pid, $pidName]);
        }

        //记录日志
        $this->outputServiceLogger("Worker[$workder_id] started.");
    }

    /**
     * 进程终止
     * @param \swoole_server $server
     * @param int $workder_id
     */
    public function onWorkerStop(\swoole_server $server, int $workder_id)
    {
        if ($workder_id == 0)
        {
            self::$pidManager->deletePid($server->worker_pid);
        }
        else
        {
            self::$pidManager->invoke('deletePid', [$server->worker_pid]);
        }
    }

    /**
     * 进程异常
     * @param \swoole_server $server
     * @param int $workder_id
     */
    public function onWorkerError(\swoole_server $server, int $workder_id)
    {
        //回放线程爆掉前的请求
        $key = 'req-wid-' . $workder_id;
        if (self::$server->table->exist($key))
        {
            $info = [
                'time' => microtime(true),
                'level' => 3,
                'message' => 'Worker Error - ' . self::$server->table->get($key, 'data'),
                'context' => [],
            ];
            self::$server->table->del($key);
            echo Formatter::convert($info) . PHP_EOL;
        }
    }

    /**
     * 通道通讯
     * @param \swoole_server $server
     * @param int $src_worker_id
     * @param string $data
     */
    public function onPipeMessage(\swoole_server $server, int $src_worker_id, string $data)
    {
        $info = unserialize($data);
        $act = $info['act'] ?? '';
        $args = $info['args'];
        switch ($act)
        {
            case 'AddTask':
                self::$taskManager->addTask(...$args);
                break;
            case 'DeliverTask':
                self::$taskManager->deliverTask($args);
                break;
            case 'FinishTask':
                self::$taskManager->finish($args);
                break;
            case 'PushTask':
                self::$taskManager->push($args);
                break;
            case 'RunTask':
                self::$server->task($args);
                break;
            case 'PidManager':
                self::$pidManager->handler($args);
                break;
        }
    }

    /**
     * 异步执行任务
     * @param \swoole_server $server
     * @param int $task_id
     * @param int $worker_id
     * @param string $data
     * @return mixed
     * @throws
     */
    public function onTask(\swoole_server $server, int $task_id, int $worker_id, string $data)
    {
        //解压内容
        $info = unserialize($data);
        if ($info == false)
        {
            self::$logger->error("onTask no data");
            return null;
        }

        //提取参数
        $cls = $info['cls'];
        $name = $info['name'];
        $args = $info['args'] ?? [];
        $md5 = $info['md5'] ?? '';
        $group = $info['group'] ?? '';

        //获取实例
        $inc = InstanceHolder::getClass($cls);

        //捕捉异常，防止发生异常下次不执行
        try
        {
            //执行任务
            $result = $inc->$name(...$args);

            //判断是否是分裂子任务
            $slave = TimerTaskHolder::getSalve($group);
            if ($slave != null && is_array($result))
            {
                $slave['md5'] = $md5;
                $slave['args'] = $result;
                if (Service::$server->worker_id == 0)
                {
                    self::$taskManager->slaveTask($slave);
                }
                else
                {
                    $info = [
                        'act' => 'SlaveTask',
                        'args' => $slave,
                    ];
                    Service::$server->sendMessage(serialize($info), 0);
                }
            }
        }
        catch (\Throwable $throwable)
        {
            //其他参数
            $error = [
                'EMSG' => $throwable->getMessage(),
                'ECODE' => $throwable->getCode(),
                'FILE' => $throwable->getFile(),
                'LINE' => $throwable->getLine()
            ];

            //调试模式下打印出文件名和行数
            if (!self::$env['deamon'])
            {
                $error['TRACE'] = $throwable->getTraceAsString();
            }

            //记录异常日志
            self::$logger->error("onTask $cls::$name error", $error);
        }

        //完成
        return $md5;
    }

    /**
     * 当任务执行完成时
     * @param \swoole_server $server
     * @param int $task_id
     * @param string $data
     */
    public function onFinish(\swoole_server $server, int $task_id, string $data)
    {
        go(function () use ($server, $data) {
            self::$taskManager->finish($data);
        });
    }

    /**
     * WebSocket连接回调
     * @param \swoole_websocket_server $server
     * @param \swoole_http_request $request
     */
    public function onOpen(\swoole_websocket_server $server, \swoole_http_request $request)
    {
        //开始时间
        $startTime = DateHelper::getTime();

        //提取数据
        $fd = $request->fd;
        $get = $request->get ?: [];

        //获取参数
        $uri = strtolower($request->server['request_uri']);
        if (substr($uri, -1, 1) != '/')
        {
            $uri .= '/';
        }
        if ($uri == '/')
        {
            self::$logger->error('there is no path defined');
            $server->close($fd);
            return;
        }

        //组装argument
        $argument = new WsArgument($server, $request->fd, $uri . 'open');
        $argument->setUserGet($get);

        //生成日志context
        $makeLoggerContext = function ($merge = []) use ($startTime, $argument) {
            return array_merge([
                'ET' => sprintf('%.3f', DateHelper::getTime() - $startTime),
                'IP' => $argument->getUserIP(),
                'TP' => 'WebSocket'
            ], $merge);
        };

        //回调
        try
        {
            $ws = new WsReceive(self::$env);
            $result = $ws->handler($argument);

            //记录连接fd
            $this->websocket_fds[$fd] = [
                'uri' => $uri,
                'get' => $get
            ];

            //输出（判断是否停止往下运行）
            if ($argument->isBreak())
            {
                return;
            }

            //向客户端输出
            if (is_array($result))
            {
                $result = json_encode($result, JSON_UNESCAPED_UNICODE);
            }
            $server->push($fd, $result);

            //记录正常日志
            self::$logger->info($argument->getUri(), $makeLoggerContext());
        }
        catch (\Throwable $throwable)
        {
            //关闭连接
            $server->close($request->fd);

            //其他参数
            $merge = [
                'EMSG' => $throwable->getMessage(),
                'ECODE' => $throwable->getCode(),
                'FILE' => $throwable->getFile(),
                'LINE' => $throwable->getLine()
            ];

            //记录异常日志
            self::$logger->error($argument->getUri(), $makeLoggerContext($merge));
        }
        finally
        {
            Context::delete();
        }
    }

    /**
     * WebSocket接收回调
     * @param \swoole_websocket_server $server
     * @param \swoole_websocket_frame $frame
     * @throws
     */
    public function onMessage(\swoole_websocket_server $server, \swoole_websocket_frame $frame)
    {
        //开始时间
        $startTime = DateHelper::getTime();

        //提取数据
        $fd = $frame->fd;
        $data = $frame->data;

        //获取处理器
        $fdInfo = $this->websocket_fds[$fd] ?? false;
        if ($fdInfo == false)
        {
            self::$logger->error('there is no uri info in [onMessage]');
            return;
        }
        $uri = $fdInfo['uri'];
        $get = $fdInfo['get'];

        //提取传入的数据
        $json = json_decode($data, true);
        if ($json == false || !is_array($json))
        {
            $uri .= 'index';
        }
        else
        {
            $cmd = array_keys($json)[0];
            if (preg_match('/^[a-zA-Z][a-zA-Z0-9_]+$/', $cmd))
            {
                $uri .= $cmd;
                $json = $json[$cmd];
            }
            else
            {
                $uri .= 'index';
            }
        }

        //组装argument
        $argument = new WsArgument($server, $fd, $uri);
        $argument->setFrame($frame);
        $argument->setUserGet($get);
        $argument->setBody(($json == false) ? $data : $json);

        //生成日志context
        $makeLoggerContext = function ($merge = []) use ($startTime, $argument) {
            return array_merge([
                'ET' => sprintf('%.3f', DateHelper::getTime() - $startTime),
                'IP' => $argument->getUserIP(),
                'TP' => 'WebSocket'
            ], $merge);
        };

        //处理请求
        $result = null;
        try
        {
            $ws = new WsReceive(self::$env);
            $result = $ws->handler($argument);

            //判断是否截断
            if ($argument->isBreak())
            {
                Context::delete();
                return;
            }

            //记录正常日志
            self::$logger->info($argument->getUri(), $makeLoggerContext());
        }
        catch (\Throwable $throwable)
        {
            //异常处理
            $inc = ExceptionHandlerHolder::getClass($throwable);
            $result = $inc->handler($argument, $throwable);

            //其他参数
            $merge = [
                'EMSG' => $throwable->getMessage(),
                'ECODE' => $throwable->getCode(),
                'FILE' => $throwable->getFile(),
                'LINE' => $throwable->getLine()
            ];

            //调试模式下打印出文件名和行数
            if (!self::$env['deamon'])
            {
                $merge['TRACE'] = $throwable->getTraceAsString();
            }

            //记录异常日志
            self::$logger->error($argument->getUri(), $makeLoggerContext($merge));
        }
        finally
        {
            //清空上下文
            Context::delete();

            //向客户端输出
            if (is_array($result))
            {
                $result = json_encode($result, JSON_UNESCAPED_UNICODE);
            }
            $server->push($fd, $result);
        }
    }

    /**
     * TCP连接回调
     * @param \swoole_server $server
     * @param int $fd
     */
    public function onConnect(\swoole_server $server, int $fd)
    {

    }

    /**
     * TCP接收回调
     * @param \swoole_server $server
     * @param int $fd 连接文件描述符
     * @param int $from_id
     * @param string $data 请求的数据
     * @throws
     */
    public function onReceive(\swoole_server $server, int $fd, int $from_id, string $data)
    {
        $data = trim($data);

        //开始时间
        $startTime = DateHelper::getTime();

        //如果是rpc类型的数据
        if (strpos($data, RpcPackage::DATA_PREFIX) === 0)
        {
            //RPC消息处理
            $this->receiveRpc($server, $fd, $from_id, $data);
            return;
        }

        //其他类型处理
        $handler = $this->getTcpHandler();
        if ($handler == false)
        {
            return;
        }

        //执行回调
        $handler->onReceive($server, $fd, $from_id, $data);

        //判断是否截断
        if ($handler->isBreak())
        {
            return;
        }

        //组装argument
        $argument = new TcpArgument($server, $fd, $handler->getUri());
        $argument->setBody($handler->getBody());

        //生成日志context
        $makeLoggerContext = function ($merge = []) use ($startTime, $argument) {
            return array_merge([
                'ET' => sprintf('%.3f', DateHelper::getTime() - $startTime),
                'IP' => $argument->getUserIP(),
                'TP' => 'TCP'
            ], $merge);
        };

        //回调
        $result = null;
        try
        {
            $tcp = new TcpReceive(self::$env);
            $result = $tcp->handler($argument);

            //判断是否截断
            if ($argument->isBreak())
            {
                Context::delete();
                return;
            }

            //记录正常日志
            self::$logger->info($argument->getUri(), $makeLoggerContext());
        }
        catch (\Throwable $throwable)
        {
            //异常处理
            $inc = ExceptionHandlerHolder::getClass($throwable);
            $result = $inc->handler($argument, $throwable);

            //其他参数
            $merge = [
                'EMSG' => $throwable->getMessage(),
                'ECODE' => $throwable->getCode(),
                'FILE' => $throwable->getFile(),
                'LINE' => $throwable->getLine()
            ];

            //记录异常日志
            self::$logger->error($argument->getUri(), $makeLoggerContext($merge));
        }
        finally
        {
            //清空上下文
            Context::delete();

            //向客户端输出
            if (is_array($result))
            {
                $result = json_encode($result, JSON_UNESCAPED_UNICODE);
            }
            $result .= "\r\n";
            $server->send($fd, $result);
        }
    }

    /**
     * URL请求回调
     * @param \swoole_http_request $request
     * @param \swoole_http_response $response
     * @throws
     */
    public function onRequest(\swoole_http_request $request, \swoole_http_response $response)
    {
        //开始时间
        $startTime = DateHelper::getTime();

        //封装参数
        $uri = strtolower($request->server['request_uri']);
        $argument = new Argument($uri, $request, $response);

        //过滤ico请求
        if ($uri == '/favicon.ico')
        {
            $argument->end();
            return;
        }

        //记录请求开始
        $reqkey = null;
        if ($this->request_handler != null)
        {
            if ($this->request_handler_instance == null)
            {
                $this->request_handler_instance = InstanceHolder::getClass($this->request_handler);
            }
            $reqkey = $this->request_handler_instance->begin($uri, $argument);
            if ($reqkey === false)
            {
                $argument->end();
                return;
            }
        }

        //生成日志context
        $makeLoggerContext = function ($merge = []) use ($startTime, $argument) {
            return array_merge([
                'ET' => sprintf('%.3f', DateHelper::getTime() - $startTime),
                'IP' => $argument->getUserIP()
            ], $merge);
        };

        //记录请求日志（目的输出至请求崩盘时回放是哪个请求产生的）
        self::$server->table->set('req-wid-' . self::$server->worker_id, ['data' => $uri]);

        //处理URL请求
        $result = null;
        try
        {
            //路由调度
            $http = new Request(self::$env);
            $http->setUri($uri);
            $result = $http->handler($argument);

            //判断是否截断
            if ($argument->isBreak())
            {
                Context::delete();
                $argument->end();
                return;
            }

            //记录正常日志
            self::$logger->info($uri, $makeLoggerContext());
        }
        catch (\Throwable $throwable)
        {
            //异常处理
            $inc = ExceptionHandlerHolder::getClass($throwable);
            $result = $inc->handler($argument, $throwable);

            //其他参数
            $merge = [
                'GET' => $request->get,
                'EMSG' => $throwable->getMessage(),
                'ECODE' => $throwable->getCode(),
                'FILE' => $throwable->getFile(),
                'LINE' => $throwable->getLine()
            ];

            //记录异常日志
            self::$logger->error($uri, $makeLoggerContext($merge));
        }
        finally
        {
            //清空当前协程上下文记录，防止内存泄露
            Context::delete();

            //格式化成字符
            if (is_array($result))
            {
                $result = json_encode($result, JSON_UNESCAPED_UNICODE);
            }

            //记录请求结束
            if ($reqkey != null)
            {
                $this->request_handler_instance->end($reqkey, $argument, $result);
            }

            //向页面输出
            $argument->end($result);
        }
    }

    /**
     * 连接关闭回调
     * @param \swoole_server $server
     * @param int $fd 连接描述符
     */
    public function onClose(\swoole_server $server, int $fd)
    {
        //连接描述符内容
        $info = $server->getClientInfo($fd);

        //WebSocket时处理
        $websocket = $info['websocket_status'] ?? 0;
        if ($websocket > 0)
        {
            $this->closeWebSocket($server, $fd);
            return;
        }

        //TCP时处理
        $this->closeTcp($server, $fd);
    }

    /**
     * 回调关闭时WS的操作
     * @param \swoole_websocket_server $server
     * @param int $fd
     */
    private function closeWebSocket(\swoole_websocket_server $server, int $fd)
    {
        //获取处理器
        $fdInfo = $this->websocket_fds[$fd] ?? false;
        if ($fdInfo == false)
        {
            return;
        }
        $uri = $fdInfo['uri'];
        $get = $fdInfo['get'];

        //组装argument
        $argument = new WsArgument($server, $fd, $uri . 'close');
        $argument->setUserGet($get);

        //处理请求
        try
        {
            $ws = new WsReceive(self::$env);
            $ws->handler($argument);
        }
        catch (\Throwable $throwable)
        {
            $merge = [
                'EMSG' => $throwable->getMessage(),
                'ECODE' => $throwable->getCode(),
                'FILE' => $throwable->getFile(),
                'LINE' => $throwable->getLine()
            ];
            self::$logger->error($argument->getUri(), $merge);
        }
        finally
        {
            unset($this->websocket_fds[$fd]);
            Context::delete();
        }
    }

    /**
     * 回调关闭时TCP的操作
     * @param \swoole_server $server
     * @param int $fd
     */
    private function closeTcp(\swoole_server $server, int $fd)
    {
        try
        {
            $handler = $this->getTcpHandler();
            if ($handler == false)
            {
                return;
            }
            $handler->onClose($server, $fd);
        }
        catch (\Throwable $throwable)
        {
            $merge = [
                'EMSG' => $throwable->getMessage(),
                'ECODE' => $throwable->getCode(),
                'FILE' => $throwable->getFile(),
                'LINE' => $throwable->getLine()
            ];
            self::$logger->error('tcp close error', $merge);
        }
        finally
        {
            Context::delete();
        }
    }

    /**
     * 启动服务器
     * @param array $opt 启动参数
     */
    public function start(array $opt)
    {
        //外部参数
        $sep = self::$env['sep'];
        $root = self::$env['root'];

        //l1、l2、l3命令兼容
        if (isset($opt['l1']))
        {
            $opt['l'] = 1;
        }
        if (isset($opt['l2']))
        {
            $opt['l'] = 2;
        }
        if (isset($opt['l3']))
        {
            $opt['l'] = 3;
        }

        //获取服务器配置
        $server_conf = Configer::get('server');
        $httpserver = $server_conf['httpserver'];
        $tcpserver = $server_conf['tcpserver'];

        //设置服务配置
        $this->timer_task = $server_conf['timer_task'] ?? false;
        $this->auto_reload = $server_conf['auto_reload'] ?? false;
        $this->request_handler = $server_conf['request_handler'] ?? false;
        self::$table_size = $server_conf['table_size'] ?? 51200;
        self::$table_col_data_size = $server_conf['table_col_data_size'] ?? 256;
        self::$table_col_num_size = $server_conf['table_col_num_size'] ?? 4;
        self::$env = array_merge(self::$env, $server_conf);
        self::$env['deamon'] = $deamon = isset($opt['d']);;

        //确保文件夹存在
        $runtime_path = $root . 'runtime' . $sep;
        $log_path = $runtime_path . 'logs' . $sep;
        if (!file_exists($log_path))
        {
            mkdir($log_path, 0777, true);
        }
        self::$env['log_path'] = $log_path;

        //swoole配置
        $opts = [
            'daemonize' => $deamon,
            'worker_num' => $server_conf['worker_num'],
            'task_worker_num' => $server_conf['task_worker_num'],
            'log_file' => $log_path . 'swoole.log',
            'enable_static_handler' => true,
            'document_root' => $root . 'static',
        ];
        if (isset($server_conf['dispatch_mode']))
        {
            $opts['dispatch_mode'] = $server_conf['dispatch_mode'];
        }
        if (isset($server_conf['socket_buffer_size']))
        {
            $opts['socket_buffer_size'] = $server_conf['socket_buffer_size'];
        }
        if (isset($server_conf['buffer_output_size']))
        {
            $opts['buffer_output_size'] = $server_conf['buffer_output_size'];
        }
        if (isset($server_conf['package_max_length']))
        {
            $opts['package_max_length'] = $server_conf['package_max_length'];
        }

        //定义内存共享表
        $table = new \swoole_table(self::$table_size);
        $table->column('data', \swoole_table::TYPE_STRING, self::$table_col_data_size);
        $table->column('num', \swoole_table::TYPE_INT, self::$table_col_num_size);
        foreach (($server_conf['table_col_items'] ?? []) as $name => $item)
        {
            if ($name == '' || $name == 'data' || $name == 'num')
            {
                continue;
            }
            $table->column($name, $item['type'], $item['size']);
        }
        $table->create();

        //服务器ID
        $table->set('id', ['data' => StringHelper::rand(5)]);

        //初始化日志
        self::$logger = new Logger(self::$env);
        if (isset($opt['l']))
        {
            self::$logger->setConfLevel(intval($opt['l']));
        }

        //HTTP Server
        $http_server = new \swoole_websocket_server($httpserver['host'], $httpserver['port']);
        $http_server->set($opts);
        $http_server->on('Open', [$this, 'onOpen']);
        $http_server->on('Start', [$this, 'onStart']);
        $http_server->on('Shutdown', [$this, 'onShutdown']);
        $http_server->on('Task', [$this, 'onTask']);
        $http_server->on('Finish', [$this, 'onFinish']);
        $http_server->on('Request', [$this, 'onRequest']);
        $http_server->on('WorkerStart', [$this, 'onWorkerStart']);
        $http_server->on('WorkerStop', [$this, 'onWorkerStop']);
        $http_server->on('WorkerError', [$this, 'onWorkerError']);
        $http_server->on('PipeMessage', [$this, 'onPipeMessage']);
        $http_server->on('Message', [$this, 'onMessage']);
        $http_server->on('Connect', [$this, 'onConnect']);
        $http_server->on('Close', [$this, 'onClose']);
        self::$server = $http_server;

        //TCP Server
        $tcp_server = $http_server->addlistener($tcpserver['host'], $tcpserver['port'], SWOOLE_SOCK_TCP);
        $tcp_server->set([
            'task_worker_num' => 0,
            'open_eof_check' => true,
            'package_eof' => "\r\n",
            'package_max_length' => 1024 * 1024 * 2, //2M
        ]);
        $tcp_server->on('receive', [$this, 'onReceive']);

        //绑定数据表
        $http_server->table = $table;

        //输出日志
        $enterLine = PHP_EOL;
        echo "                                 __  {$enterLine}       ______      ______  _____/ /__{$enterLine}      / ___/ | /| / / __ \/ ___/ //_/{$enterLine}     (__  )| |/ |/ / /_/ / /  / ,<   {$enterLine}    /____/ |__/|__/\____/_/  /_/|_| {$enterLine}{$enterLine}";
        $this->outputServiceLogger('PHP version ' . phpversion(), '#');
        $this->outputServiceLogger('Swoole version ' . phpversion('swoole'), '#');
        $this->outputServiceLogger('Swork version ' . Version::getComposer(self::$env), '#');
        $this->outputServiceLogger('App running at:', '*');
        $this->outputServiceLogger(" - Tcp:  \e[32m{$tcpserver['host']}:{$tcpserver['port']}\e[0m", '*');
        $this->outputServiceLogger(" - Http: \e[32m{$httpserver['host']}:{$httpserver['port']}\e[0m", '*');

        //增加用户进程
        $userProcess = new UserProcess();
        $userProcess->init($http_server);

        //如果开启代码热更新
        if ($this->auto_reload)
        {
            $reload = new Reload(self::$env);
            $reload->init();
        }

        //Start;
        $http_server->start();

        //等待子进程结束运行
        $userProcess->waitToExit();
    }

    /**
     * 输出服务日志
     * @param string $message
     * @param string $flag
     */
    private function outputServiceLogger(string $message, string $flag = '*')
    {
        //时间信息
        $time = round(microtime(true), 3);
        $nums = explode('.', $time);
        $t1 = $nums[0];
        $t2 = '000';
        if (count($nums) >= 2)
        {
            $t2 = str_pad($nums[1], 3, '0', STR_PAD_RIGHT);
        }
        $time = date('Y-m-d H:i:s', $t1) . '.' . $t2;

        //输出
        echo $time . " $flag " . $message . PHP_EOL;
    }

    /**
     * 处理Rpc信息
     * @param \swoole_server $server
     * @param int $fd
     * @param int $from_id
     * @param string $data
     */
    private function receiveRpc(\swoole_server $server, int $fd, int $from_id, string $data)
    {
        //初始化
        $cmd = null;
        $res = null;
        $error = null;

        //捕捉异常
        try
        {
            //数据解包
            $info = RpcPackage::unserialize($data);
            if ($info == false)
            {
                throw new RpcException('Rpc receive empty data');
            }
            if (empty($info['cmd']))
            {
                throw new RpcException('Rpc receive miss [cmd]');
            }
            if (!isset($info['data']) && !is_null($info['data']))
            {
                throw new RpcException('Rpc receive miss [data]');
            }
            $cmd = $info['cmd'];

            //处理
            $tcp = new RpcReceive($server, self::$env);
            $res = $tcp->handler($fd, $from_id, $cmd, $info['data']);
        }
        catch (\Throwable $e)
        {
            $error = [
                'code' => $e->getCode(),
                'msg' => $e->getMessage()
            ];
            self::$logger->error("onReceive error", [$e->getMessage()]);
        }

        //数据组包
        $pack = RpcPackage::serialize($cmd, $res, $error);

        //回送数据
        $server->send($fd, $pack);
    }

    /**
     * 获取tcp回调实例
     * @return TcpHandlerInterface|null
     */
    private function getTcpHandler()
    {
        $cls = Configer::get('server:tcp_handler', '');
        $ins = null;
        if (empty($cls))
        {
            $ins = new TcpHandler();
        }
        else
        {
            $ins = InstanceHolder::getClass($cls);
        }
        return $ins;
    }
}
